﻿using GN.Pay.Core.Entities;
using GN.Pay.Core.Exceptions;
using GN.Pay.Core.Repositories;
using GN.Pay.Core.Values;
using GN.Pay.TradeChannel;
using GN.Pay.Utils;
using GN.Pay.Utils.Net.Http;
using System;
using System.Linq;
using System.Collections.Generic;
using System.Text;
using System.Globalization;
using System.Threading.Tasks;
using GN.Pay.Utils.Security;
using GN.Pay.Core.Tasks.Queue;
using static GN.Pay.Core.Consts.ServiceConstant;
using GN.Pay.Core.Tasks;
using static GN.Pay.Core.Consts;
using Abp.Dependency;
using GN.Pay.TradeChannel.Transfers;
using System.Web;

namespace GN.Pay.Core.Services.Impl
{
    /// <summary>
    /// 交易通知服务
    /// </summary>
    class TradeNotifyService : APayService, ITradeNotifyService
    {
        private readonly ITradeCacheService tradeCacheService;
        private readonly IQueueManager queueManager;
        private readonly ITradeNotifyRepository repostiory;
        private readonly IIocManager iocManager;

        /// <summary>
        /// 
        /// </summary>
        /// <param name="repostiory"></param>
        /// <param name="tradeCacheService"></param>
        /// <param name="queueManager"></param>
        public TradeNotifyService(IIocManager iocManager, ITradeNotifyRepository repostiory,
            ITradeCacheService tradeCacheService,
            IQueueManager queueManager)
        {
            this.iocManager = iocManager;
            this.repostiory = repostiory;
            this.tradeCacheService = tradeCacheService;
            this.queueManager = queueManager;
        }

        /// <summary>
        /// 付款通知
        /// </summary>
        /// <param name="order">订单</param>
        public void PayNotify(ITradeOrderQueryResult order)
        {
            _PayNotify(order, null);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="order"></param>
        /// <param name="delay"></param>
        public void PayNotify(ITradeOrderQueryResult order, TimeSpan delay)
        {
            _PayNotify(order, delay);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="order"></param>
        /// <param name="delay"></param>
        private void _PayNotify(ITradeOrderQueryResult order, TimeSpan? delay)
        {
            if (order == null)
            {
                return;
            }
            try
            {
                var msg = new RabbitMQ.GNClient.ObjectMessage(order);
                msg.MessageId = order.MessageSign(Start.MessageSign);
                queueManager.Publish(QueueType.ChannelPayNotify, msg, delay);
            }
            catch (Exception err)
            {
                this.Logger.Error("接收付款通知处理异常:", err);
                QueueError(QueueResetMessage.ResetType_ChannelPayNotify, order.OrderNo, order);
                throw;
            }
        }

        /// <summary>
        /// 退款通知
        /// </summary>
        /// <param name="apply">结果</param>
        public void RefundNotify(ITradeRefundApplyResult apply)
        {
            if (apply == null)
            {
                return;
            }
            if (apply.ResultState == TradeApplyResultState.Complete_Success)
            {
                try
                {
                    var msg = new RabbitMQ.GNClient.ObjectMessage(apply);
                    msg.MessageId = apply.MessageSign(Start.MessageSign);
                    queueManager.Publish(QueueType.ChannelRefundNotify, msg);
                }
                catch (Exception err)
                {
                    this.Logger.Error("接收退款通知处理异常:", err);
                    QueueError(QueueResetMessage.ResetType_ChannelRefundNotify, apply.RefundNo, apply);
                    throw;
                }
            }
        }

        /// <summary>
        /// 转账通知
        /// </summary>
        /// <param name="result">结果</param>
        public void TransfersNotify(ITransfersApplyResult result)
        {
            if (result == null)
            {
                return;
            }
            if (result.ResultState == TradeApplyResultState.Complete_Success)
            {
                try
                {
                    var msg = new RabbitMQ.GNClient.ObjectMessage(result);
                    msg.MessageId = result.MessageSign(Start.MessageSign);
                    queueManager.Publish(QueueType.ChannelTransfersNotify, msg);
                }
                catch (Exception err)
                {
                    this.Logger.Error("接收转账通知处理异常:", err);
                    QueueError(QueueResetMessage.ResetType_ChannelTransfersNotify, result.TransfersNo, result);
                    throw;
                }
            }
        }

        /// <summary>
        /// 队列出错
        /// </summary>
        /// <param name="apply"></param>
        private void QueueError(int type, string key, object result)
        {
            if (result == null)
            {
                return;
            }
            TaskMonitor.Increment(TaskConstant.TaskMonitorType.NotifyErrorQueueType);
            this.UpdateUnitOfWork(() =>
            {
                var queueRepository = iocManager.Resolve<IQueueErrorMessageRepository>();
                var item = queueRepository.FirstOrDefault(s => s.MessageKey == key && s.MessageType == type);
                if (item == null)
                {
                    item = new QueueErrorMessage()
                    {
                        Created = DateTime.Now,
                        MessageKey = key,
                        MessageType = type,
                        MessageContent = result.ToJson(true)
                    };
                    queueRepository.Insert(item);
                }
            });
        }

        /// <summary>
        /// 放入
        /// </summary>
        /// <param name="app"></param>
        /// <param name="notifyType"></param>
        /// <param name="url"></param>
        /// <param name="returnUrl"></param>
        /// <param name="message"></param>
        /// <returns></returns>
        public TradeNotify PullNotify(AMerchantAppInfo app, int notifyType, string notifyUrl,
            string returnUrl, INotifyData message)
        {
            TradeNotify notify = new TradeNotify()
            {
                AppId = app.Id,
                CreationTime = DateTime.Now,
                NotifyType = notifyType,
                NotifyUrl = notifyUrl,
                ReturnUrl = returnUrl,
                UrlParms = message.ToJson(true),
                Count = 0,
                Status = 1,
                TradeNo = message.TradeNo,
                NotifyMsg = "",
            };
            notify.DataSign(app.SignKey);
            if (!string.IsNullOrWhiteSpace(notifyUrl))
            {
                this.repostiory.Insert(notify);
                this.repostiory.SaveChanges();
                var msg = new RabbitMQ.GNClient.ObjectMessage(notify);
                msg.MessageId = "0";
                queueManager.Publish(QueueType.ClientHttpNotify, msg);
            }
            if (notifyType == TradeNotifyType.Pay)
            {
                try
                {
                    tradeCacheService.SetPayReturnNotifyAsync(notify);
                }
                catch (Exception err)
                {
                    this.Logger.Error("保存付款通知绶存出错", err);
                }
            }
            return notify;
        }

        /// <summary>
        /// Http Post 通知
        /// </summary>
        /// <param name="notify">通知</param>
        /// <returns></returns>
        public Tuple<bool, string> ClientNotify(TradeNotify notify)
        {
            if (notify == null)
            {
                PayExceptionUtils.ThrowPayValidationException("notify 对象为 null");
            }
            var app = tradeCacheService.GetMerchantAppInfo(notify.AppId);
            if (app == null)
            {
                PayExceptionUtils.ThrowPaySystemException("无法获取 AppId :" + notify.AppId + " 的应用");
            }
            if (!notify.DataSginVerify(app.SignKey))
            {
                PayExceptionUtils.ThrowPaySignException("通知签名验证不通过，可能人为修改数据");
            }
            SignDictionary signDic = Sign(notify);
            return this.HttpPostNotify(notify, signDic);
        }

        /// <summary>
        /// 转为骆锋命名，即首个字母为小写
        /// </summary>
        /// <param name="value">值</param>
        /// <param name="culture">区域</param>
        /// <returns></returns>
        private string ToCamelCase(string value)
        {
            if (string.IsNullOrWhiteSpace(value))
            {
                return value;
            }
            if (value.Length == 1)
            {
                return value.ToLower(CultureInfo.InvariantCulture);
            }
            return char.ToLower(value[0], CultureInfo.InvariantCulture) + value.Substring(1);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="notify"></param>
        /// <returns></returns>
        public SignDictionary Sign(TradeNotify notify)
        {
            var dic = notify.UrlParms.JsonToObject<Dictionary<string, object>>(true);
            SignDictionary signDic = new SignDictionary();
            foreach (var kv in dic)
            {
                if (kv.Value != null)
                {
                    signDic[ToCamelCase(kv.Key)] = kv.Value.ToString();
                }
            }
            var app = tradeCacheService.GetMerchantAppInfo(notify.AppId);
            if (app == null)
            {
                PayExceptionUtils.ThrowPaySystemException("无法获取 AppId :" + notify.AppId + " 的应用");
            }
            signDic["merchantId"] = app.MerchantId;
            signDic["appId"] = notify.AppId;
            signDic["randomStr"] = Guid.NewGuid().ToString("N");
            signDic["timeStamp"] = DateTimeUtils.CurrentUtcTimeStampMilliseconds.ToString();
            string urlParams = signDic.UrlLinkString() + "&key=" + app.AppKey;
            signDic["sign"] = Md5Utils.Sign(urlParams, Encoding.UTF8);
            return signDic;
        }

        /// <summary>
        /// 请求
        /// </summary>
        /// <param name="notify"></param>
        /// <param name="notifyParams"></param>
        /// <param name="responseString"></param>
        /// <returns></returns>
        private Tuple<bool, string> HttpPostNotify(TradeNotify notify, IDictionary<string, string> notifyParams)
        {
            var dic = new Dictionary<string, string>();
            foreach (var item in notifyParams)
            {
                dic[item.Key] = HttpUtility.UrlEncode(item.Value, Encoding.UTF8);
            }
            var responseString = HttpUtils.PostString(notify.NotifyUrl, dic);
            if (responseString != null && "Success".Equals(responseString, StringComparison.InvariantCultureIgnoreCase))
            {
                notify.Status = 99;
                notify.NotifyMsg = "通知成功";
                UpdateNotify(notify);
                this.Logger.InfoFormat("通知成功 Post:{0} UrlParams:{1}",
                   notify.NotifyUrl, notify.UrlParms);
                return new Tuple<bool, string>(true, responseString);
            }
            else
            {
                return new Tuple<bool, string>(false, responseString);
            }
        }

        /// <summary>
        /// 更新通知
        /// </summary>
        /// <param name="notify"></param>
        /// <param name="addErrorCount"></param>
        /// <param name="errorMsg"></param>
        public TradeNotify UpdateNotify(TradeNotify notify)
        {
            if (notify == null)
            {
                PayExceptionUtils.ThrowPayValidationException("notify 对象为 null");
            }
            var entity = repostiory.FirstOrDefault(s => s.Id == notify.Id);
            if (entity != null)
            {
                entity.Count = notify.Count;
                entity.Status = notify.Status;
                entity.LastNotifyTime = notify.LastNotifyTime;
                entity.NotifyMsg = notify.NotifyMsg;
                this.repostiory.Update(entity);
                return entity;
            }
            else
            {
                return notify;
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="currentId"></param>
        /// <param name="takeCount"></param>
        /// <returns></returns>
        public List<TradeNotify> ScanItems(long currentId, int takeCount)
        {
            var last = DateTime.Now.AddMinutes(-10);
            return this.repostiory.GetAll()
                .Where(c => c.Id > currentId && c.Status >= 0 && (c.LastNotifyTime == null
                || c.LastNotifyTime <= last))
                .OrderBy(c => c.Id)
                .Take(takeCount).ToList();
        }


    }
}
